/*
 * Logger.java
 *
 * Created on May 17, 2007, 6:11 PM
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.plugin;

import java.io.OutputStream;
import java.io.PrintStream;
import tal.drivechain.chain.ChainNode;
import tal.drivechain.chain.ChainNode.MSG;

/**
 * Classes implementing this interface are responsible for receiving logging message from plugins
 * and using them.
 * <p>
 * There are 5 message types:
 * Plugin.MSG.INFO  -   This message should be displayed only when the user asks for extensive
 *                      runtime information.
 * Plugin.MSG.DEBUG -   This type is for messages containing debug information for patch debug
 *                      or plugin debug. Will be displayed only when debug info is requested by
 *                      the user.
 * Plugin.MSG.WARNING - These messages must always be sent to the user and contain important
 *                      warnings about problem with the execution of the application.
 * Plugin.MSG.ERROR -   These messages must also always be displayed and by sending this
 *                      message type the plugin is requesting the termination of the 
 *                      application. Telling that it encountered an unrecoverable error.
 * Plugin.MSG.ANOUNCE - These messages are used when the plugin wants to send a message to the
 *                      user that must be displayed at any case. This is the same as MSG.WARNING
 *                      except that it doesn't mean an error has occured.
 *
 * @author Tal Eisenberg
 */

public abstract class Logger {
    protected boolean timing = false;
    protected ModulePackageManager loggerPackage = null;
    private long timingRef = 0;
    protected PrintStream outputStream;
    protected LogLine currentLog = new LogLine();
    protected Limiter limiter = new Limiter(40);

    public void setPackageManager(ModulePackageManager loggerPackage) {
        this.loggerPackage = loggerPackage;
    }

    public void runLogger() {
        limiter.start();
    }

    public void stopLogger() {
        limiter.interrupt();
    }
    /**
     * Sends a message to Logger.
     * 
     * 
     * @param message       The message that will be used by the logger
     * @param type          The message type. Any of the MSG constants in {@link Module}
     * @param moduleName    The name of the module that sent the message.
     */
    public void nodeMessage(String message, MSG type, ChainNode nodeSrc) {
        generalMessage(message, type, (nodeSrc==null?"???":nodeSrc.getPath()));
    }
    
    public void generalMessage(String message, MSG type, String src) {
        synchronized(this) {
            currentLog.message = message;
            currentLog.type = type;
            currentLog.src = src;
            if (timing)
                currentLog.time = timeDelta();
            else currentLog.time = -1;
            currentLog.passed = false;
        }
    }
    
    public void initTiming() {
        timingRef = System.nanoTime();
    }
    
    private double timeDelta() {
        long curtime = System.nanoTime();
        double time = ((double)(curtime-timingRef))/1000000;
        timingRef = curtime;
        return time;
    }
    
    public void init(boolean timing, OutputStream out) {
        setTiming(timing);
        if (out!=null)
            this.outputStream = new PrintStream(out);
        else this.outputStream = null;
    }
        
    public void setTiming(boolean timing) {
        if (this.timing==timing) return;
        this.timing = timing;
        if (printsTiming())
            initTiming();
    }

    public boolean printsTiming() { return timing; }

    public class LogLine {
        public String message;
        public MSG type;
        public String src;
        public double time;
        public boolean passed = false;
    }

    public class Limiter extends Thread {
        private long limiterFreq;

        public Limiter(long limiterFreq) { this.limiterFreq = limiterFreq; }

        @Override
        public void run() {
            while (true) {
                if (!currentLog.passed) {
                    synchronized(this) {
                        logReceived(currentLog);
                        currentLog.passed = true;
                    }
                }
                try {
                    sleep(limiterFreq);
                } catch (InterruptedException ex) { return; }

            }
        }
    }

    public abstract void logReceived(LogLine currentLog);
    public abstract void println(String line);

}